Notebook accompanying the manuscipt of Floriddia et. al 2020, Distinct oligodendrocyte populations have spatial preference and different responses to spinal cord injury.

Here we load the data of the first round of single cell sequencing and take samples “GC_7_CC” and “TC_6_CC” to integrate.
Previous runs without integration showed similar results, meaning they are not really experiencing batch effects, but I perform integration anyway to align them as best possible.

anno_10x
   GC_51    GD_52    TC_47    TC_50 GC_32_MC  GC_7_CC GD_30_CC GD_31_MC  GD_3_MC  TC_1_MC  TC_6_CC 
    1039     2850      903     6192      891     1055     2584     2438      252     2266     2037 

Now we perform QC, looking at the percentage of mitochondrial RNA vs other RNA, plus other metrics.
* nFeature_RNA = number of genes
* nCount_RNA = number of UMIs or Counts
* percent.mt = percent of expression of mitochondrial genes versus the rest

# The [[ operator can add columns to object metadata. This is a great place to stash QC stats
oligos[["percent.mt"]] <- PercentageFeatureSet(oligos, pattern = "^mt-")
# Visualize QC metrics as a violin plot
VlnPlot(oligos, group.by = "Sample",features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3,pt.size = 0.1)

The two samples seem comparable QC-wise, so now we plot the QC information in another way to see if we can estimate thesholds for removing bad cells and perhaps doublets.

# FeatureScatter is typically used to visualize feature-feature relationships, but can be used
# for anything calculated by the object, i.e. columns in object metadata, PC scores etc.
plot1 <- FeatureScatter(oligos, group.by = "Sample",feature1 = "nCount_RNA", feature2 = "percent.mt",pt.size = 0.1)
plot2 <- FeatureScatter(oligos, group.by = "Sample", feature1 = "nCount_RNA", feature2 = "nFeature_RNA",pt.size = 0.1)
CombinePlots(plots = list(plot1, plot2))
CombinePlots is being deprecated. Plots should now be combined using the patchwork system.

These samples seem to be performing similarly, which is a great sign for the integration
Now we will remove cells expressing less that 200 genes (to remove bad cells),
and more than 3000 genes (to remove doublets). And remove cells expressing more that 5% mitochondrial genes.

#Clean up the data
oligos <- subset(oligos, subset = nFeature_RNA > 200 & nFeature_RNA < 3000 & percent.mt < 10)

Optional code to integrate the object (we did not for the paper)

Generating the UMAP and TSNE.

oligos.integrated <- RunPCA(oligos.integrated, verbose = FALSE)
ElbowPlot(oligos.integrated)

oligos.integrated <- RunUMAP(oligos.integrated, dims = 1:30)
19:37:54 UMAP embedding parameters a = 0.9922 b = 1.112
19:37:54 Read 3042 rows and found 30 numeric columns
19:37:54 Using Annoy for neighbor search, n_neighbors = 30
19:37:54 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
19:37:55 Writing NN index file to temp file /var/folders/98/j14xl9ln5190l9fvq32g5rkw0000gn/T//RtmpCflbt4/file104fd76a18b09
19:37:55 Searching Annoy index using 1 thread, search_k = 3000
19:37:55 Annoy recall = 100%
19:37:56 Commencing smooth kNN distance calibration using 1 thread
19:37:56 Initializing from normalized Laplacian + noise
19:37:59 Commencing optimization for 500 epochs, with 134676 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
19:38:04 Optimization finished
oligos.integrated <- RunTSNE(oligos.integrated, dims = 1:30)
plots <- DimPlot(oligos.integrated, group.by = c("Sample"), combine = FALSE)
plots <- lapply(X = plots, FUN = function(x) x + theme(legend.position = "top") + guides(color = guide_legend(nrow = 3, 
    byrow = TRUE, override.aes = list(size = 3))))
CombinePlots(plots)
CombinePlots is being deprecated. Plots should now be combined using the patchwork system.

plots <- TSNEPlot(oligos.integrated, group.by = c("Sample"), combine = FALSE)
plots <- lapply(X = plots, FUN = function(x) x + theme(legend.position = "top") + guides(color = guide_legend(nrow = 3, 
    byrow = TRUE, override.aes = list(size = 3))))
CombinePlots(plots)
CombinePlots is being deprecated. Plots should now be combined using the patchwork system.

Here I show expression of some common genes that I know are supposed to be more or less stable clusters within the OLs, just for reference.

DefaultAssay(oligos.integrated) <- "RNA"
# Normalize RNA data for visualization purposes
oligos.integrated <- NormalizeData(oligos.integrated, verbose = FALSE)
FeaturePlot(oligos.integrated, c("Pdgfra", "Ptprz1","Bmp4","Itpr2", "Egr1", "Klk6", "Hopx", "Ptgds","Il33"),pt.size = 0.1)

DefaultAssay(oligos.integrated) <- "integrated"

Here I set the clustering to be specific for at least the tiny cluster of Astrocytes hiding in the middle of the UMAP. COPs are not included, not even with higher clustering resolutions, meaning I can only get them by subclustering. This might be because the COPs are such a tiny cluster in this data, and they express many markers that OPCs are expressing as well.
I show the clusters on the UMAP so you can see their position.

oligos.integrated <- FindNeighbors(oligos.integrated, dims = 1:30)
Computing nearest neighbor graph
Computing SNN
oligos.integrated <- FindClusters(oligos.integrated,resolution = 0.8)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 3042
Number of edges: 169348

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.7834
Number of communities: 12
Elapsed time: 0 seconds
DimPlot(oligos.integrated, group.by = c("seurat_clusters"), combine = FALSE)
[[1]]

Below you will find a table of the top 2 markers found for each cluster. pct means percentage of expression, where pct.2 refers to all the cells not in the tested cluster.

oligos.integrated.markers %>% group_by(cluster) %>% top_n(n = 2, wt = avg_logFC)

Below follows the heatmap showing the top 10 genes based on fold change for each cluster.

DefaultAssay(oligos.integrated) <- "SCT"
top10 <- oligos.integrated.markers %>% group_by(cluster) %>% top_n(n = 20, wt = avg_logFC)
DoHeatmap(oligos.integrated, features = top10$gene) + NoLegend()

library(viridis)

And here are the top 2 genes found for each cluster as show on the UMAP.

DefaultAssay(oligos.integrated) <- "RNA"
# Normalize RNA data for visualization purposes
oligos.integrated <- NormalizeData(oligos.integrated, verbose = FALSE)
top2 <- oligos.integrated.markers %>% group_by(cluster) %>% top_n(n = 2, wt = avg_logFC)
FeaturePlot(oligos.integrated, features = top2$gene,pt.size = 0.1)

Label transfer

Now we attempt to transfer the cluster labels of the Science dataset onto the 10X dataset.

oligos.integrated$predicted.id <- factor(oligos.integrated$predicted.id,levels=c("OPC","COP","NFOL1","MFOL1","MFOL2","MOL1","MOL2","MOL3","MOL4","MOL5","MOL6"))
DimPlot(oligos.integrated, group.by = c("seurat_clusters"), combine = FALSE)
[[1]]

DimPlot(oligos.integrated, group.by = c("predicted.id"), combine = FALSE)
[[1]]

DimPlot(oligos.integrated, group.by = c("Sample"), combine = FALSE)
[[1]]

barplot(table(oligos.integrated$Sample,oligos.integrated$predicted.id))

data <- as.data.frame(table(oligos.integrated$Sample,oligos.integrated$predicted.id))
colnames(data) <- c("Condition","Cluster","Freq")
library(plyr)
data$Cluster  <- factor(data$Cluster,levels=c("OPC","COP","NFOL1","MFOL1","MFOL2","MOL1","MOL2","MOL3","MOL4","MOL5","MOL6"))
data$Cluster  <- revalue(as.factor(data$Cluster),c("PPR"="VLMC"))
The following `from` values were not present in `x`: PPR
# Stacked + percent
ggplot(data, aes(fill=Condition, y=Freq, x=Cluster)) + 
    geom_bar(position="fill", stat="identity")

barplot(table(oligos.integrated$Sample,oligos.integrated$seurat_clusters))

data <- as.data.frame(table(oligos.integrated$Sample,oligos.integrated$seurat_clusters))
colnames(data) <- c("Condition","Cluster","Freq")
library(plyr)
# Stacked + percent
ggplot(data, aes(fill=Condition, y=Freq, x=Cluster)) + 
    geom_bar(position="fill", stat="identity")

data <- as.data.frame(table(oligos.integrated$Sample,oligos.integrated$predicted.id))
colnames(data) <- c("Condition","Cluster","Freq")
library(plyr)
data$Cluster  <- factor(data$Cluster,levels=c("OPC","COP","NFOL1","MFOL1","MFOL2","MOL1","MOL2","MOL3","MOL4","MOL5","MOL6"))
library(reshape2)
datacasted <- dcast(data,Cluster ~ Condition)
Using Freq as value column: use value.var to override.
calc_cpm <-function (expr_mat) 
{
    norm_factor <- colSums(expr_mat)
    return(t(t(expr_mat)/norm_factor)) * 10^50
}
datacasted[,2:3] <- calc_cpm(datacasted[,2:3])
data <- melt(datacasted)
Using Cluster as id variables
colnames(data) <- c("Condition","Cluster","Freq")
#data$Cluster  <- revalue(as.factor(data$Cluster),c("PPR"="VLMC"))
# Stacked + percent
ggplot(data, aes(fill=Condition, y=Freq, x=Cluster)) + 
    geom_bar(position="fill", stat="identity")

ggplot(data, aes(fill=Cluster, y=Freq, x=Condition)) + 
    geom_bar( stat="identity")

row.names(datacasted) <- datacasted[,1]
datacasted <- datacasted[,2:3]*100
datamelted <- melt(t(datacasted))
ggplot(datamelted, aes(y = value, x = Var2)) + # Move y and x here so than they can be used in stat_*
    geom_dotplot(aes(fill = Var1),   # Use fill = Species here not in ggplot()
                 binaxis = "y",         # which axis to bin along
                 binwidth = 2,        # Minimal difference considered diffeerent
                 stackdir = "center",
                 position = position_jitter(0.2)# Centered
                 ) +  # scale_y_log10() + 
    stat_summary(fun.y = mean, fun.ymin = mean, fun.ymax = mean,
                 geom = "crossbar", width = 0.5,fatten = 0.01) + theme(axis.text.x = element_text(angle = 45))

oligos.integrated.markers %>% group_by(cluster) %>% top_n(n = 2, wt = avg_logFC)
VlnPlot(oligos.integrated, group.by = "predicted.id",features = c("Ptprz1","Serpine2","Egr1","Egr2","Dusp1","Fosb","Klk6","Hopx","Anxa5","Ptgds","Grm3","Car2","Il33"), ncol = 1,pt.size = 0.1)

library(viridis)
DefaultAssay(oligos.integrated) <- "integrated"
top10 <- oligos.integrated.markers %>% group_by(cluster) %>% top_n(n = 10, wt = avg_logFC)
DoHeatmap(oligos.integrated,features = top10$gene) + NoLegend() +scale_fill_viridis()
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing
scale.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKTm90ZWJvb2sgYWNjb21wYW55aW5nIHRoZSBtYW51c2NpcHQgb2YgRmxvcmlkZGlhIGV0LiBhbCAyMDIwLCBEaXN0aW5jdCBvbGlnb2RlbmRyb2N5dGUgcG9wdWxhdGlvbnMgaGF2ZSBzcGF0aWFsIHByZWZlcmVuY2UgYW5kIGRpZmZlcmVudCByZXNwb25zZXMgdG8gc3BpbmFsIGNvcmQgaW5qdXJ5LgoKSGVyZSB3ZSBsb2FkIHRoZSBkYXRhIG9mIHRoZSBmaXJzdCByb3VuZCBvZiBzaW5nbGUgY2VsbCBzZXF1ZW5jaW5nIGFuZCB0YWtlIHNhbXBsZXMgXyJHQ183X0NDIl8gYW5kIF8iVENfNl9DQyJfIHRvIGludGVncmF0ZS4gIApQcmV2aW91cyBydW5zIHdpdGhvdXQgaW50ZWdyYXRpb24gc2hvd2VkIHNpbWlsYXIgcmVzdWx0cywgbWVhbmluZyB0aGV5IGFyZSBub3QgcmVhbGx5IGV4cGVyaWVuY2luZyBiYXRjaCBlZmZlY3RzLCBidXQgSSBwZXJmb3JtIGludGVncmF0aW9uIGFueXdheSB0byBhbGlnbiB0aGVtIGFzIGJlc3QgcG9zc2libGUuCmBgYHtyIGVjaG89RkFMU0V9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGdncGxvdDIpCm9wdGlvbnMoZnV0dXJlLmdsb2JhbHMubWF4U2l6ZSA9IDQwMDAgKiAxMDI0XjIpCiNMb2FkIGRhdGEKbG9hZCgifi9Eb2N1bWVudHMvU2luZ2xlQ2VsbERhdGEvTmV0d29ya2NsdXN0ZXJpbmcvRWxpc2FBbmFseXNpcy9FdmVyeXRoaW5nQ29tYmluZWQuUmRhdGEiKQpjZWxsc3RvdXNlIDwtIGludGVyc2VjdChjb2xuYW1lcyhlbWF0XzEweCkscm93Lm5hbWVzKGFubm9fMTB4KSkKZW1hdF8xMHggPC0gZW1hdF8xMHhbLGNlbGxzdG91c2VdCmFubm9fMTB4IDwtIGFubm9fMTB4W2NlbGxzdG91c2UsXQp0YWJsZShhbm5vXzEweCkKI1NlbGVjdCBvbmx5IGNvcnB1c2NhbGxvc3VtIGRhdGEKZW1hdF8xMHggPC0gZW1hdF8xMHhbLGFubm9fMTB4ICVpbiUgYygiR0NfN19DQyIsIlRDXzZfQ0MiKV0KYW5ub18xMHggPC0gYXMuY2hhcmFjdGVyKGFubm9fMTB4KQpuYW1lcyhhbm5vXzEweCkgPC0gY2VsbHN0b3VzZQphbm5vXzEweCA8LSBhbm5vXzEweFtjb2xuYW1lcyhlbWF0XzEweCldCiNjb2xuYW1lcyhhbm5vXzEweCkgPC0gIlNhbXBsZSIKYW5ub18xMHggPC0gYXMuZGF0YS5mcmFtZShhbm5vXzEweCxzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmNvbG5hbWVzKGFubm9fMTB4KSA8LSAiU2FtcGxlIgojUHV0IGluIFNldXJhdCBvYmplY3QgYW5kIHNwbGl0IGluIHR3byB0byBwZXJmb3JtIHByZXBub3JtYWxpemF0aW9uCm9saWdvcyA8LSBDcmVhdGVTZXVyYXRPYmplY3QoZW1hdF8xMHgsIG1ldGEuZGF0YSA9ICBhbm5vXzEweCxtaW4uY2VsbHMgPSAzLCBtaW4uZmVhdHVyZXMgPSAyMDApCmBgYAogIApOb3cgd2UgcGVyZm9ybSBRQywgbG9va2luZyBhdCB0aGUgcGVyY2VudGFnZSBvZiAqKm1pdG9jaG9uZHJpYWwgUk5BKiogdnMgKipvdGhlciBSTkEqKiwgcGx1cyBvdGhlciBtZXRyaWNzLiAgCiogbkZlYXR1cmVfUk5BID0gbnVtYmVyIG9mIGdlbmVzICAKKiBuQ291bnRfUk5BID0gbnVtYmVyIG9mIFVNSXMgb3IgQ291bnRzICAKKiBwZXJjZW50Lm10ID0gcGVyY2VudCBvZiBleHByZXNzaW9uIG9mIG1pdG9jaG9uZHJpYWwgZ2VuZXMgdmVyc3VzIHRoZSByZXN0CmBgYHtyIGVjaG89VFJVRX0KIyBUaGUgW1sgb3BlcmF0b3IgY2FuIGFkZCBjb2x1bW5zIHRvIG9iamVjdCBtZXRhZGF0YS4gVGhpcyBpcyBhIGdyZWF0IHBsYWNlIHRvIHN0YXNoIFFDIHN0YXRzCm9saWdvc1tbInBlcmNlbnQubXQiXV0gPC0gUGVyY2VudGFnZUZlYXR1cmVTZXQob2xpZ29zLCBwYXR0ZXJuID0gIl5tdC0iKQojIFZpc3VhbGl6ZSBRQyBtZXRyaWNzIGFzIGEgdmlvbGluIHBsb3QKVmxuUGxvdChvbGlnb3MsIGdyb3VwLmJ5ID0gIlNhbXBsZSIsZmVhdHVyZXMgPSBjKCJuRmVhdHVyZV9STkEiLCAibkNvdW50X1JOQSIsICJwZXJjZW50Lm10IiksIG5jb2wgPSAzLHB0LnNpemUgPSAwLjEpCmBgYAogIApUaGUgdHdvIHNhbXBsZXMgc2VlbSBjb21wYXJhYmxlIFFDLXdpc2UsIHNvIG5vdyB3ZSBwbG90IHRoZSBRQyBpbmZvcm1hdGlvbiBpbiBhbm90aGVyIHdheSB0byBzZWUgaWYgd2UgY2FuIGVzdGltYXRlIHRoZXNob2xkcyBmb3IgcmVtb3ZpbmcgYmFkIGNlbGxzIGFuZCBwZXJoYXBzIGRvdWJsZXRzLgpgYGB7ciBlY2hvPVRSVUV9CiMgRmVhdHVyZVNjYXR0ZXIgaXMgdHlwaWNhbGx5IHVzZWQgdG8gdmlzdWFsaXplIGZlYXR1cmUtZmVhdHVyZSByZWxhdGlvbnNoaXBzLCBidXQgY2FuIGJlIHVzZWQKIyBmb3IgYW55dGhpbmcgY2FsY3VsYXRlZCBieSB0aGUgb2JqZWN0LCBpLmUuIGNvbHVtbnMgaW4gb2JqZWN0IG1ldGFkYXRhLCBQQyBzY29yZXMgZXRjLgpwbG90MSA8LSBGZWF0dXJlU2NhdHRlcihvbGlnb3MsIGdyb3VwLmJ5ID0gIlNhbXBsZSIsZmVhdHVyZTEgPSAibkNvdW50X1JOQSIsIGZlYXR1cmUyID0gInBlcmNlbnQubXQiLHB0LnNpemUgPSAwLjEpCnBsb3QyIDwtIEZlYXR1cmVTY2F0dGVyKG9saWdvcywgZ3JvdXAuYnkgPSAiU2FtcGxlIiwgZmVhdHVyZTEgPSAibkNvdW50X1JOQSIsIGZlYXR1cmUyID0gIm5GZWF0dXJlX1JOQSIscHQuc2l6ZSA9IDAuMSkKQ29tYmluZVBsb3RzKHBsb3RzID0gbGlzdChwbG90MSwgcGxvdDIpKQpgYGAKICAKVGhlc2Ugc2FtcGxlcyBzZWVtIHRvIGJlIHBlcmZvcm1pbmcgc2ltaWxhcmx5LCB3aGljaCBpcyBhIGdyZWF0IHNpZ24gZm9yIHRoZSBpbnRlZ3JhdGlvbiAgCk5vdyB3ZSB3aWxsIHJlbW92ZSBjZWxscyBleHByZXNzaW5nIGxlc3MgdGhhdCAyMDAgZ2VuZXMgKHRvIHJlbW92ZSBiYWQgY2VsbHMpLCAgIAphbmQgbW9yZSB0aGFuIDMwMDAgZ2VuZXMgKHRvIHJlbW92ZSBkb3VibGV0cykuIEFuZCByZW1vdmUgY2VsbHMgZXhwcmVzc2luZyBtb3JlIHRoYXQgNSUgbWl0b2Nob25kcmlhbCBnZW5lcy4KYGBge3J9CiNDbGVhbiB1cCB0aGUgZGF0YQpvbGlnb3MgPC0gc3Vic2V0KG9saWdvcywgc3Vic2V0ID0gbkZlYXR1cmVfUk5BID4gMjAwICYgbkZlYXR1cmVfUk5BIDwgMzAwMCAmIHBlcmNlbnQubXQgPCAxMCkKYGBgCk9wdGlvbmFsIGNvZGUgdG8gaW50ZWdyYXRlIHRoZSBvYmplY3QgKHdlIGRpZCBub3QgZm9yIHRoZSBwYXBlcikKYGBge3IgaW5jbHVkZT1GQUxTRX0KI29saWdvcy5pbnRlZ3JhdGVkIDwtIFNDVHJhbnNmb3JtKG9saWdvcyx2ZXJib3NlID0gRkFMU0UpCm9saWdvcy5saXN0IDwtIFNwbGl0T2JqZWN0KG9saWdvcywgc3BsaXQuYnkgPSAiU2FtcGxlIikKZm9yIChpIGluIDE6bGVuZ3RoKG9saWdvcy5saXN0KSkgewogICAgb2xpZ29zLmxpc3RbW2ldXSA8LSBTQ1RyYW5zZm9ybShvbGlnb3MubGlzdFtbaV1dLCB2ZXJib3NlID0gRkFMU0UpCn0KYGBgCmBgYHtyIGluY2x1ZGU9RkFMU0V9CiNpbnRlZ3JhdGUKb2xpZ29zLmZlYXR1cmVzIDwtIFNlbGVjdEludGVncmF0aW9uRmVhdHVyZXMob2JqZWN0Lmxpc3QgPSBvbGlnb3MubGlzdCwgbmZlYXR1cmVzID0gMzAwMCkKb2xpZ29zLmxpc3QgPC0gUHJlcFNDVEludGVncmF0aW9uKG9iamVjdC5saXN0ID0gb2xpZ29zLmxpc3QsIGFuY2hvci5mZWF0dXJlcyA9IG9saWdvcy5mZWF0dXJlcywKICAgIHZlcmJvc2UgPSBGQUxTRSkKb2xpZ29zLmFuY2hvcnMgPC0gRmluZEludGVncmF0aW9uQW5jaG9ycyhvYmplY3QubGlzdCA9IG9saWdvcy5saXN0LCBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJTQ1QiLAogICAgYW5jaG9yLmZlYXR1cmVzID0gb2xpZ29zLmZlYXR1cmVzLCB2ZXJib3NlID0gRkFMU0UpCm9saWdvcy5pbnRlZ3JhdGVkIDwtIEludGVncmF0ZURhdGEoYW5jaG9yc2V0ID0gb2xpZ29zLmFuY2hvcnMsIG5vcm1hbGl6YXRpb24ubWV0aG9kID0gIlNDVCIsCiAgICB2ZXJib3NlID0gRkFMU0UpCmBgYAogIApHZW5lcmF0aW5nIHRoZSBVTUFQIGFuZCBUU05FLgpgYGB7cn0Kb2xpZ29zLmludGVncmF0ZWQgPC0gUnVuUENBKG9saWdvcy5pbnRlZ3JhdGVkLCB2ZXJib3NlID0gRkFMU0UpCkVsYm93UGxvdChvbGlnb3MuaW50ZWdyYXRlZCkKb2xpZ29zLmludGVncmF0ZWQgPC0gUnVuVU1BUChvbGlnb3MuaW50ZWdyYXRlZCwgZGltcyA9IDE6MzApCm9saWdvcy5pbnRlZ3JhdGVkIDwtIFJ1blRTTkUob2xpZ29zLmludGVncmF0ZWQsIGRpbXMgPSAxOjMwKQpwbG90cyA8LSBEaW1QbG90KG9saWdvcy5pbnRlZ3JhdGVkLCBncm91cC5ieSA9IGMoIlNhbXBsZSIpLCBjb21iaW5lID0gRkFMU0UpCnBsb3RzIDwtIGxhcHBseShYID0gcGxvdHMsIEZVTiA9IGZ1bmN0aW9uKHgpIHggKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikgKyBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQobnJvdyA9IDMsIAogICAgYnlyb3cgPSBUUlVFLCBvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzKSkpKQpDb21iaW5lUGxvdHMocGxvdHMpCnBsb3RzIDwtIFRTTkVQbG90KG9saWdvcy5pbnRlZ3JhdGVkLCBncm91cC5ieSA9IGMoIlNhbXBsZSIpLCBjb21iaW5lID0gRkFMU0UpCnBsb3RzIDwtIGxhcHBseShYID0gcGxvdHMsIEZVTiA9IGZ1bmN0aW9uKHgpIHggKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikgKyBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQobnJvdyA9IDMsIAogICAgYnlyb3cgPSBUUlVFLCBvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzKSkpKQpDb21iaW5lUGxvdHMocGxvdHMpCmBgYAogIApIZXJlIEkgc2hvdyBleHByZXNzaW9uIG9mIHNvbWUgY29tbW9uIGdlbmVzIHRoYXQgSSBrbm93IGFyZSBzdXBwb3NlZCB0byBiZSBtb3JlIG9yIGxlc3Mgc3RhYmxlIGNsdXN0ZXJzIHdpdGhpbiB0aGUgT0xzLCBqdXN0IGZvciByZWZlcmVuY2UuCmBgYHtyIGZpZy53aWR0aD0xMH0KRGVmYXVsdEFzc2F5KG9saWdvcy5pbnRlZ3JhdGVkKSA8LSAiUk5BIgojIE5vcm1hbGl6ZSBSTkEgZGF0YSBmb3IgdmlzdWFsaXphdGlvbiBwdXJwb3NlcwpvbGlnb3MuaW50ZWdyYXRlZCA8LSBOb3JtYWxpemVEYXRhKG9saWdvcy5pbnRlZ3JhdGVkLCB2ZXJib3NlID0gRkFMU0UpCkZlYXR1cmVQbG90KG9saWdvcy5pbnRlZ3JhdGVkLCBjKCJQZGdmcmEiLCAiUHRwcnoxIiwiQm1wNCIsIkl0cHIyIiwgIkVncjEiLCAiS2xrNiIsICJIb3B4IiwgIlB0Z2RzIiwiSWwzMyIpLHB0LnNpemUgPSAwLjEpCkRlZmF1bHRBc3NheShvbGlnb3MuaW50ZWdyYXRlZCkgPC0gImludGVncmF0ZWQiCmBgYAogIApIZXJlIEkgc2V0IHRoZSBjbHVzdGVyaW5nIHRvIGJlIHNwZWNpZmljIGZvciBhdCBsZWFzdCB0aGUgdGlueSBjbHVzdGVyIG9mIEFzdHJvY3l0ZXMgaGlkaW5nIGluIHRoZSBtaWRkbGUgb2YgdGhlIFVNQVAuIENPUHMgYXJlIG5vdCBpbmNsdWRlZCwgbm90IGV2ZW4gd2l0aCBoaWdoZXIgY2x1c3RlcmluZyByZXNvbHV0aW9ucywgbWVhbmluZyBJIGNhbiBvbmx5IGdldCB0aGVtIGJ5IHN1YmNsdXN0ZXJpbmcuIFRoaXMgbWlnaHQgYmUgYmVjYXVzZSB0aGUgQ09QcyBhcmUgc3VjaCBhIHRpbnkgY2x1c3RlciBpbiB0aGlzIGRhdGEsIGFuZCB0aGV5IGV4cHJlc3MgbWFueSBtYXJrZXJzIHRoYXQgT1BDcyBhcmUgZXhwcmVzc2luZyBhcyB3ZWxsLiAgCkkgc2hvdyB0aGUgY2x1c3RlcnMgb24gdGhlIFVNQVAgc28geW91IGNhbiBzZWUgdGhlaXIgcG9zaXRpb24uCmBgYHtyfQpvbGlnb3MuaW50ZWdyYXRlZCA8LSBGaW5kTmVpZ2hib3JzKG9saWdvcy5pbnRlZ3JhdGVkLCBkaW1zID0gMTozMCkKb2xpZ29zLmludGVncmF0ZWQgPC0gRmluZENsdXN0ZXJzKG9saWdvcy5pbnRlZ3JhdGVkLHJlc29sdXRpb24gPSAwLjgpCmBgYApgYGB7cn0KRGltUGxvdChvbGlnb3MuaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSBjKCJzZXVyYXRfY2x1c3RlcnMiKSwgY29tYmluZSA9IEZBTFNFKQpgYGAKICAKQmVsb3cgeW91IHdpbGwgZmluZCBhIHRhYmxlIG9mIHRoZSB0b3AgMiBtYXJrZXJzIGZvdW5kIGZvciBlYWNoIGNsdXN0ZXIuIHBjdCBtZWFucyBwZXJjZW50YWdlIG9mIGV4cHJlc3Npb24sIHdoZXJlIHBjdC4yIHJlZmVycyB0byBhbGwgdGhlIGNlbGxzIG5vdCBpbiB0aGUgdGVzdGVkIGNsdXN0ZXIuCmBgYHtyIGluY2x1ZGU9RkFMU0V9CiMgZmluZCBtYXJrZXJzIGZvciBldmVyeSBjbHVzdGVyIGNvbXBhcmVkIHRvIGFsbCByZW1haW5pbmcgY2VsbHMsIHJlcG9ydCBvbmx5IHRoZSBwb3NpdGl2ZSBvbmVzCmxpYnJhcnkoZHBseXIpCm9saWdvcy5pbnRlZ3JhdGVkLm1hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMob2xpZ29zLmludGVncmF0ZWQsIG9ubHkucG9zID0gVFJVRSwgbWluLnBjdCA9IDAuMjUsIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMjUpCmBgYApgYGB7cn0Kb2xpZ29zLmludGVncmF0ZWQubWFya2VycyAlPiUgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIHRvcF9uKG4gPSAyLCB3dCA9IGF2Z19sb2dGQykKYGBgCiAgCkJlbG93IGZvbGxvd3MgdGhlIGhlYXRtYXAgc2hvd2luZyB0aGUgdG9wIDEwIGdlbmVzIGJhc2VkIG9uIGZvbGQgY2hhbmdlIGZvciBlYWNoIGNsdXN0ZXIuICAKYGBge3IgZmlnLndpZHRoPTEwfQpEZWZhdWx0QXNzYXkob2xpZ29zLmludGVncmF0ZWQpIDwtICJTQ1QiCnRvcDEwIDwtIG9saWdvcy5pbnRlZ3JhdGVkLm1hcmtlcnMgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSB0b3BfbihuID0gMjAsIHd0ID0gYXZnX2xvZ0ZDKQpEb0hlYXRtYXAob2xpZ29zLmludGVncmF0ZWQsIGZlYXR1cmVzID0gdG9wMTAkZ2VuZSkgKyBOb0xlZ2VuZCgpCmxpYnJhcnkodmlyaWRpcykKYGBgCgpBbmQgaGVyZSBhcmUgdGhlIHRvcCAyIGdlbmVzIGZvdW5kIGZvciBlYWNoIGNsdXN0ZXIgYXMgc2hvdyBvbiB0aGUgVU1BUC4KYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpEZWZhdWx0QXNzYXkob2xpZ29zLmludGVncmF0ZWQpIDwtICJSTkEiCiMgTm9ybWFsaXplIFJOQSBkYXRhIGZvciB2aXN1YWxpemF0aW9uIHB1cnBvc2VzCm9saWdvcy5pbnRlZ3JhdGVkIDwtIE5vcm1hbGl6ZURhdGEob2xpZ29zLmludGVncmF0ZWQsIHZlcmJvc2UgPSBGQUxTRSkKdG9wMiA8LSBvbGlnb3MuaW50ZWdyYXRlZC5tYXJrZXJzICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgdG9wX24obiA9IDIsIHd0ID0gYXZnX2xvZ0ZDKQpGZWF0dXJlUGxvdChvbGlnb3MuaW50ZWdyYXRlZCwgZmVhdHVyZXMgPSB0b3AyJGdlbmUscHQuc2l6ZSA9IDAuMSkKYGBgCiMjIyMgTGFiZWwgdHJhbnNmZXIKTm93IHdlIGF0dGVtcHQgdG8gdHJhbnNmZXIgdGhlIGNsdXN0ZXIgbGFiZWxzIG9mIHRoZSBTY2llbmNlIGRhdGFzZXQgb250byB0aGUgMTBYIGRhdGFzZXQuCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmxvYWQoIn4vRG9jdW1lbnRzL1NpbmdsZUNlbGxEYXRhL1NjaWVuY2VkYXRhc2V0L1NjaWVuY2VtYXRyaWNlc2Fubm8uUmRhdGEiKQphbm5vX3NjaWVuY2UkU2FtcGxlIDwtIHJlcCgiU2NpZW5jZSIsbmNvbChlbWF0X3NjaWVuY2UpKQpTY2llbmNlIDwtIENyZWF0ZVNldXJhdE9iamVjdChlbWF0X3NjaWVuY2UsIG1ldGEuZGF0YSA9ICBhbm5vX3NjaWVuY2UsbWluLmNlbGxzID0gMywgbWluLmZlYXR1cmVzID0gMjAwKQpTY2llbmNlIDwtIFNDVHJhbnNmb3JtKFNjaWVuY2UsIG1pbl9jZWxscz0zLHZlcmJvc2UgPSBGQUxTRSkKCkRlZmF1bHRBc3NheShvbGlnb3MuaW50ZWdyYXRlZCkgPC0gIlNDVCIKCm9saWdvcy5hbmNob3JzIDwtIEZpbmRUcmFuc2ZlckFuY2hvcnMocmVmZXJlbmNlID0gU2NpZW5jZSwgcXVlcnkgPW9saWdvcy5pbnRlZ3JhdGVkLCBkaW1zID0gMToxNSxwcm9qZWN0LnF1ZXJ5ID0gVCkgCnByZWRpY3Rpb25zIDwtIFRyYW5zZmVyRGF0YShhbmNob3JzZXQgPSBvbGlnb3MuYW5jaG9ycywgcmVmZGF0YSA9IFNjaWVuY2UkY2VsbF9jbGFzcywgZGltcyA9IDE6MTUpCm9saWdvcy5pbnRlZ3JhdGVkIDwtIEFkZE1ldGFEYXRhKG9saWdvcy5pbnRlZ3JhdGVkLCBtZXRhZGF0YSA9IHByZWRpY3Rpb25zKQpgYGAKYGBge3J9Cm9saWdvcy5pbnRlZ3JhdGVkJHByZWRpY3RlZC5pZCA8LSBmYWN0b3Iob2xpZ29zLmludGVncmF0ZWQkcHJlZGljdGVkLmlkLGxldmVscz1jKCJPUEMiLCJDT1AiLCJORk9MMSIsIk1GT0wxIiwiTUZPTDIiLCJNT0wxIiwiTU9MMiIsIk1PTDMiLCJNT0w0IiwiTU9MNSIsIk1PTDYiKSkKRGltUGxvdChvbGlnb3MuaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSBjKCJzZXVyYXRfY2x1c3RlcnMiKSwgY29tYmluZSA9IEZBTFNFKQpEaW1QbG90KG9saWdvcy5pbnRlZ3JhdGVkLCBncm91cC5ieSA9IGMoInByZWRpY3RlZC5pZCIpLCBjb21iaW5lID0gRkFMU0UpCkRpbVBsb3Qob2xpZ29zLmludGVncmF0ZWQsIGdyb3VwLmJ5ID0gYygiU2FtcGxlIiksIGNvbWJpbmUgPSBGQUxTRSkKYGBgCgoKYGBge3J9CmJhcnBsb3QodGFibGUob2xpZ29zLmludGVncmF0ZWQkU2FtcGxlLG9saWdvcy5pbnRlZ3JhdGVkJHByZWRpY3RlZC5pZCkpCmRhdGEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShvbGlnb3MuaW50ZWdyYXRlZCRTYW1wbGUsb2xpZ29zLmludGVncmF0ZWQkcHJlZGljdGVkLmlkKSkKY29sbmFtZXMoZGF0YSkgPC0gYygiQ29uZGl0aW9uIiwiQ2x1c3RlciIsIkZyZXEiKQpsaWJyYXJ5KHBseXIpCmRhdGEkQ2x1c3RlciAgPC0gZmFjdG9yKGRhdGEkQ2x1c3RlcixsZXZlbHM9YygiT1BDIiwiQ09QIiwiTkZPTDEiLCJNRk9MMSIsIk1GT0wyIiwiTU9MMSIsIk1PTDIiLCJNT0wzIiwiTU9MNCIsIk1PTDUiLCJNT0w2IikpCmRhdGEkQ2x1c3RlciAgPC0gcmV2YWx1ZShhcy5mYWN0b3IoZGF0YSRDbHVzdGVyKSxjKCJQUFIiPSJWTE1DIikpCiMgU3RhY2tlZCArIHBlcmNlbnQKZ2dwbG90KGRhdGEsIGFlcyhmaWxsPUNvbmRpdGlvbiwgeT1GcmVxLCB4PUNsdXN0ZXIpKSArIAogICAgZ2VvbV9iYXIocG9zaXRpb249ImZpbGwiLCBzdGF0PSJpZGVudGl0eSIpCgoKCmJhcnBsb3QodGFibGUob2xpZ29zLmludGVncmF0ZWQkU2FtcGxlLG9saWdvcy5pbnRlZ3JhdGVkJHNldXJhdF9jbHVzdGVycykpCmRhdGEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShvbGlnb3MuaW50ZWdyYXRlZCRTYW1wbGUsb2xpZ29zLmludGVncmF0ZWQkc2V1cmF0X2NsdXN0ZXJzKSkKY29sbmFtZXMoZGF0YSkgPC0gYygiQ29uZGl0aW9uIiwiQ2x1c3RlciIsIkZyZXEiKQpsaWJyYXJ5KHBseXIpCiMgU3RhY2tlZCArIHBlcmNlbnQKZ2dwbG90KGRhdGEsIGFlcyhmaWxsPUNvbmRpdGlvbiwgeT1GcmVxLCB4PUNsdXN0ZXIpKSArIAogICAgZ2VvbV9iYXIocG9zaXRpb249ImZpbGwiLCBzdGF0PSJpZGVudGl0eSIpCgpgYGAKYGBge3J9CmRhdGEgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShvbGlnb3MuaW50ZWdyYXRlZCRTYW1wbGUsb2xpZ29zLmludGVncmF0ZWQkcHJlZGljdGVkLmlkKSkKY29sbmFtZXMoZGF0YSkgPC0gYygiQ29uZGl0aW9uIiwiQ2x1c3RlciIsIkZyZXEiKQpsaWJyYXJ5KHBseXIpCmRhdGEkQ2x1c3RlciAgPC0gZmFjdG9yKGRhdGEkQ2x1c3RlcixsZXZlbHM9YygiT1BDIiwiQ09QIiwiTkZPTDEiLCJNRk9MMSIsIk1GT0wyIiwiTU9MMSIsIk1PTDIiLCJNT0wzIiwiTU9MNCIsIk1PTDUiLCJNT0w2IikpCmxpYnJhcnkocmVzaGFwZTIpCmRhdGFjYXN0ZWQgPC0gZGNhc3QoZGF0YSxDbHVzdGVyIH4gQ29uZGl0aW9uKQpjYWxjX2NwbSA8LWZ1bmN0aW9uIChleHByX21hdCkgCnsKICAgIG5vcm1fZmFjdG9yIDwtIGNvbFN1bXMoZXhwcl9tYXQpCiAgICByZXR1cm4odCh0KGV4cHJfbWF0KS9ub3JtX2ZhY3RvcikpICogMTBeNTAKfQpkYXRhY2FzdGVkWywyOjNdIDwtIGNhbGNfY3BtKGRhdGFjYXN0ZWRbLDI6M10pCmRhdGEgPC0gbWVsdChkYXRhY2FzdGVkKQpjb2xuYW1lcyhkYXRhKSA8LSBjKCJDb25kaXRpb24iLCJDbHVzdGVyIiwiRnJlcSIpCiNkYXRhJENsdXN0ZXIgIDwtIHJldmFsdWUoYXMuZmFjdG9yKGRhdGEkQ2x1c3RlciksYygiUFBSIj0iVkxNQyIpKQojIFN0YWNrZWQgKyBwZXJjZW50CmdncGxvdChkYXRhLCBhZXMoZmlsbD1Db25kaXRpb24sIHk9RnJlcSwgeD1DbHVzdGVyKSkgKyAKICAgIGdlb21fYmFyKHBvc2l0aW9uPSJmaWxsIiwgc3RhdD0iaWRlbnRpdHkiKQpnZ3Bsb3QoZGF0YSwgYWVzKGZpbGw9Q2x1c3RlciwgeT1GcmVxLCB4PUNvbmRpdGlvbikpICsgCiAgICBnZW9tX2Jhciggc3RhdD0iaWRlbnRpdHkiKQoKcm93Lm5hbWVzKGRhdGFjYXN0ZWQpIDwtIGRhdGFjYXN0ZWRbLDFdCmRhdGFjYXN0ZWQgPC0gZGF0YWNhc3RlZFssMjozXSoxMDAKZGF0YW1lbHRlZCA8LSBtZWx0KHQoZGF0YWNhc3RlZCkpCgpnZ3Bsb3QoZGF0YW1lbHRlZCwgYWVzKHkgPSB2YWx1ZSwgeCA9IFZhcjIpKSArICMgTW92ZSB5IGFuZCB4IGhlcmUgc28gdGhhbiB0aGV5IGNhbiBiZSB1c2VkIGluIHN0YXRfKgogICAgZ2VvbV9kb3RwbG90KGFlcyhmaWxsID0gVmFyMSksICAgIyBVc2UgZmlsbCA9IFNwZWNpZXMgaGVyZSBub3QgaW4gZ2dwbG90KCkKICAgICAgICAgICAgICAgICBiaW5heGlzID0gInkiLCAgICAgICAgICMgd2hpY2ggYXhpcyB0byBiaW4gYWxvbmcKICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDIsICAgICAgICAjIE1pbmltYWwgZGlmZmVyZW5jZSBjb25zaWRlcmVkIGRpZmZlZXJlbnQKICAgICAgICAgICAgICAgICBzdGFja2RpciA9ICJjZW50ZXIiLAogICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKDAuMikjIENlbnRlcmVkCiAgICAgICAgICAgICAgICAgKSArICAjIHNjYWxlX3lfbG9nMTAoKSArIAogICAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZnVuLnltaW4gPSBtZWFuLCBmdW4ueW1heCA9IG1lYW4sCiAgICAgICAgICAgICAgICAgZ2VvbSA9ICJjcm9zc2JhciIsIHdpZHRoID0gMC41LGZhdHRlbiA9IDAuMDEpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSkpCmBgYApgYGB7ciBpbmNsdWRlPUZBTFNFfQojIGZpbmQgbWFya2VycyBmb3IgZXZlcnkgY2x1c3RlciBjb21wYXJlZCB0byBhbGwgcmVtYWluaW5nIGNlbGxzLCByZXBvcnQgb25seSB0aGUgcG9zaXRpdmUgb25lcwpEZWZhdWx0QXNzYXkob2xpZ29zLmludGVncmF0ZWQpIDwtICJTQ1QiCklkZW50cyhvbGlnb3MuaW50ZWdyYXRlZCkgPC0gInByZWRpY3RlZC5pZCIKbGlicmFyeShkcGx5cikKb2xpZ29zLmludGVncmF0ZWQubWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhvbGlnb3MuaW50ZWdyYXRlZCwgb25seS5wb3MgPSBUUlVFLCBtaW4ucGN0ID0gMC4yNSwgbG9nZmMudGhyZXNob2xkID0gMC4xKQpgYGAKYGBge3J9Cm9saWdvcy5pbnRlZ3JhdGVkLm1hcmtlcnMgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSB0b3BfbihuID0gMiwgd3QgPSBhdmdfbG9nRkMpCmBgYApgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9NH0KVmxuUGxvdChvbGlnb3MuaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkIixmZWF0dXJlcyA9IGMoIlB0cHJ6MSIsIlNlcnBpbmUyIiwiRWdyMSIsIkVncjIiLCJEdXNwMSIsIkZvc2IiLCJLbGs2IiwiSG9weCIsIkFueGE1IiwiUHRnZHMiLCJHcm0zIiwiQ2FyMiIsIklsMzMiKSwgbmNvbCA9IDEscHQuc2l6ZSA9IDAuMSkKYGBgCmBgYHtyIGZpZy53aWR0aD0xMH0KbGlicmFyeSh2aXJpZGlzKQpEZWZhdWx0QXNzYXkob2xpZ29zLmludGVncmF0ZWQpIDwtICJpbnRlZ3JhdGVkIgp0b3AxMCA8LSBvbGlnb3MuaW50ZWdyYXRlZC5tYXJrZXJzICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgdG9wX24obiA9IDEwLCB3dCA9IGF2Z19sb2dGQykKCkRvSGVhdG1hcChvbGlnb3MuaW50ZWdyYXRlZCxmZWF0dXJlcyA9IHRvcDEwJGdlbmUpICsgTm9MZWdlbmQoKSArc2NhbGVfZmlsbF92aXJpZGlzKCkKYGBg